﻿/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace DaveSexton.DocProject
{
	public class BindableStringCollection : Collection<BindableStringCollection.BindableString>, IBindingList
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="BindableStringCollection" /> class.
		/// </summary>
		public BindableStringCollection()
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="BindableStringCollection" /> class.
		/// </summary>
		public BindableStringCollection(IList<string> list)
		{
			if (list == null)
				throw new ArgumentNullException("list");

			foreach (string s in list)
				Add(s);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="BindableStringCollection" /> class.
		/// </summary>
		public BindableStringCollection(IList<BindableString> list)
			: base(list)
		{
		}
		#endregion

		#region Methods
		public ReadOnlyCollection<BindableStringCollection.BindableString> AsReadOnly()
		{
			return new ReadOnlyCollection<BindableStringCollection.BindableString>(this);
		}

		protected override void ClearItems()
		{
			foreach (BindableString item in this)
				item.PropertyChanged -= item_PropertyChanged;

			base.ClearItems();

			OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
		}

		protected override void InsertItem(int index, BindableString item)
		{
			item.PropertyChanged += item_PropertyChanged;

			base.InsertItem(index, item);

			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
		}

		protected override void RemoveItem(int index)
		{
			this[index].PropertyChanged -= item_PropertyChanged;

			base.RemoveItem(index);

			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
		}

		protected override void SetItem(int index, BindableString item)
		{
			this[index].PropertyChanged -= item_PropertyChanged;

			item.PropertyChanged += item_PropertyChanged;

			base.SetItem(index, item);

			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
		}
		#endregion

		#region Events
		private readonly object ListChangedEventLock = new object();
		private ListChangedEventHandler ListChangedEvent;

		/// <summary>
		/// Event raised when the collection changes.
		/// </summary>
		public event ListChangedEventHandler ListChanged
		{
			add
			{
				lock (ListChangedEventLock)
				{
					ListChangedEvent += value;
				}
			}
			remove
			{
				lock (ListChangedEventLock)
				{
					ListChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ListChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="ListChangedEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnListChanged(ListChangedEventArgs e)
		{
			ListChangedEventHandler handler = null;

			lock (ListChangedEventLock)
			{
				handler = ListChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, -1));
		}
		#endregion

		#region IBindingList Members
		public virtual void AddIndex(PropertyDescriptor property)
		{
		}

		public virtual object AddNew()
		{
			BindableString s = new BindableString();
			this.Add(s);
			return s;
		}

		public virtual bool AllowEdit
		{
			get { return true; }
		}

		public virtual bool AllowNew
		{
			get { return true; }
		}

		public virtual bool AllowRemove
		{
			get { return true; }
		}

		public virtual void ApplySort(PropertyDescriptor property, ListSortDirection direction)
		{
			throw new NotSupportedException();
		}

		public virtual int Find(PropertyDescriptor property, object key)
		{
			throw new NotSupportedException();
		}

		public virtual bool IsSorted
		{
			get { return false; }
		}

		public virtual void RemoveIndex(PropertyDescriptor property)
		{
		}

		public virtual void RemoveSort()
		{
		}

		public virtual ListSortDirection SortDirection
		{
			get { return ListSortDirection.Ascending; }
		}

		public virtual PropertyDescriptor SortProperty
		{
			get
			{
				return null;
			}
		}

		public virtual bool SupportsChangeNotification
		{
			get { return true; }
		}

		public virtual bool SupportsSearching
		{
			get { return false; }
		}

		public virtual bool SupportsSorting
		{
			get { return false; }
		}
		#endregion

		#region Nested
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
		public sealed class BindableString : IEquatable<BindableString>, INotifyPropertyChanged
		{
			#region Public Properties
			public string Value
			{
				get
				{
					return value;
				}
				set
				{
					if (value == null)
						throw new ArgumentNullException("value");

					if (this.value != value)
					{
						this.value = value;

						OnPropertyChanged(new PropertyChangedEventArgs("Value"));
					}
				}
			}

			public int Length
			{
				get
				{
					return value.Length;
				}
			}
			#endregion

			#region Private / Protected
			private string value;
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="BindableString" /> structure.
			/// </summary>
			public BindableString()
			{
				value = string.Empty;
			}

			/// <summary>
			/// Constructs a new instance of the <see cref="BindableString" /> structure.
			/// </summary>
			public BindableString(string value)
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this.value = value;
			}
			#endregion

			#region Methods
			[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
			public override bool Equals(object obj)
			{
				return (obj is BindableString && Equals((BindableString) obj))
					|| (obj is string && value == (string) obj);
			}

			public bool Equals(BindableString other)
			{
				return string.Equals(value, other.value);
			}

			public override int GetHashCode()
			{
				return value.GetHashCode();
			}

			public override string ToString()
			{
				return value;
			}
			#endregion

			#region Operator Overloads
			public static bool operator ==(BindableString value1, BindableString value2)
			{
				return value1.Equals(value2);
			}

			public static bool operator !=(BindableString value1, BindableString value2)
			{
				return !value1.Equals(value2);
			}

			public static implicit operator string(BindableString wrapper)
			{
				return wrapper.value;
			}

			public static implicit operator BindableString(string value)
			{
				return (value == null) ? null : new BindableString(value);
			}
			#endregion

			#region INotifyPropertyChanged Members
			private readonly object PropertyChangedEventLock = new object();
			private PropertyChangedEventHandler PropertyChangedEvent;

			/// <summary>
			/// Event raised when the <see cref="Value"/> changes.
			/// </summary>
			public event PropertyChangedEventHandler PropertyChanged
			{
				add
				{
					lock (PropertyChangedEventLock)
					{
						PropertyChangedEvent += value;
					}
				}
				remove
				{
					lock (PropertyChangedEventLock)
					{
						PropertyChangedEvent -= value;
					}
				}
			}

			/// <summary>
			/// Raises the <see cref="PropertyChanged" /> event.
			/// </summary>
			/// <param name="e"><see cref="PropertyChangedEventArgs" /> object that provides the arguments for the event.</param>
			private void OnPropertyChanged(PropertyChangedEventArgs e)
			{
				PropertyChangedEventHandler handler = null;

				lock (PropertyChangedEventLock)
				{
					handler = PropertyChangedEvent;
				}

				if (handler != null)
					handler(this, e);
			}
			#endregion
		}
		#endregion
	}
}
